/***************************************************************************\
 * Copyright (C) by Keio University
 * Utils.cpp created in 11 2011.
 * Mail : fdesorbi@hvrl.ics.keio.ac.jp
 *
 * Utils.cpp is part of the HVRL Engine Library.
 *
 * The HVRL Engine Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * The HVRL Engine Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
\***************************************************************************/


#include <hvrl/gpgpu/opencl/Utils.hpp>
#include <hvrl/common/Common.hpp>

namespace hvrl
{

namespace opencl
{

static cl_platform_id platform_id = NULL;
static cl_device_id device_id = NULL;
static cl_uint ret_num_devices = 0;
static cl_uint ret_num_platforms = 0;
static cl_context context;
static bool initialized = false;
static cl_command_queue command_queue;

std::string errorToString(const cl_int& err)
{
    switch (err)
    {
    case CL_SUCCESS:
        return "Success!";
    case CL_DEVICE_NOT_FOUND:
        return "Device not found.";
    case CL_DEVICE_NOT_AVAILABLE:
        return "Device not available";
    case CL_COMPILER_NOT_AVAILABLE:
        return "Compiler not available";
    case CL_MEM_OBJECT_ALLOCATION_FAILURE:
        return "Memory object allocation failure";
    case CL_OUT_OF_RESOURCES:
        return "Out of resources";
    case CL_OUT_OF_HOST_MEMORY:
        return "Out of host memory";
    case CL_PROFILING_INFO_NOT_AVAILABLE:
        return "Profiling information not available";
    case CL_MEM_COPY_OVERLAP:
        return "Memory copy overlap";
    case CL_IMAGE_FORMAT_MISMATCH:
        return "Image format mismatch";
    case CL_IMAGE_FORMAT_NOT_SUPPORTED:
        return "Image format not supported";
    case CL_BUILD_PROGRAM_FAILURE:
        return "Program build failure";
    case CL_MAP_FAILURE:
        return "Map failure";
    case CL_INVALID_VALUE:
        return "Invalid value";
    case CL_INVALID_DEVICE_TYPE:
        return "Invalid device type";
    case CL_INVALID_PLATFORM:
        return "Invalid platform";
    case CL_INVALID_DEVICE:
        return "Invalid device";
    case CL_INVALID_CONTEXT:
        return "Invalid context";
    case CL_INVALID_QUEUE_PROPERTIES:
        return "Invalid queue properties";
    case CL_INVALID_COMMAND_QUEUE:
        return "Invalid command queue";
    case CL_INVALID_HOST_PTR:
        return "Invalid host pointer";
    case CL_INVALID_MEM_OBJECT:
        return "Invalid memory object";
    case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR:
        return "Invalid image format descriptor";
    case CL_INVALID_IMAGE_SIZE:
        return "Invalid image size";
    case CL_INVALID_SAMPLER:
        return "Invalid sampler";
    case CL_INVALID_BINARY:
        return "Invalid binary";
    case CL_INVALID_BUILD_OPTIONS:
        return "Invalid build options";
    case CL_INVALID_PROGRAM:
        return "Invalid program";
    case CL_INVALID_PROGRAM_EXECUTABLE:
        return "Invalid program executable";
    case CL_INVALID_KERNEL_NAME:
        return "Invalid kernel name";
    case CL_INVALID_KERNEL_DEFINITION:
        return "Invalid kernel definition";
    case CL_INVALID_KERNEL:
        return "Invalid kernel";
    case CL_INVALID_ARG_INDEX:
        return "Invalid argument index";
    case CL_INVALID_ARG_VALUE:
        return "Invalid argument value";
    case CL_INVALID_ARG_SIZE:
        return "Invalid argument size";
    case CL_INVALID_KERNEL_ARGS:
        return "Invalid kernel arguments";
    case CL_INVALID_WORK_DIMENSION:
        return "Invalid work dimension";
    case CL_INVALID_WORK_GROUP_SIZE:
        return "Invalid work group size";
    case CL_INVALID_WORK_ITEM_SIZE:
        return "Invalid work item size";
    case CL_INVALID_GLOBAL_OFFSET:
        return "Invalid global offset";
    case CL_INVALID_EVENT_WAIT_LIST:
        return "Invalid event wait list";
    case CL_INVALID_EVENT:
        return "Invalid event";
    case CL_INVALID_OPERATION:
        return "Invalid operation";
    case CL_INVALID_GL_OBJECT:
        return "Invalid OpenGL object";
    case CL_INVALID_BUFFER_SIZE:
        return "Invalid buffer size";
    case CL_INVALID_MIP_LEVEL:
        return "Invalid mip-map level";
    default:
        return "Unknown";
    }
}


bool init(void)
{
    if(initialized)
    {
        return true;
    }

    cl_int error;

    // Get platform and device information
    error = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
    if(error != CL_SUCCESS)
    {
        Log::add().error("initDevice",errorToString(error));
        return false;
    }

    error = clGetDeviceIDs( platform_id, CL_DEVICE_TYPE_DEFAULT, 1,&device_id, &ret_num_devices);
    if(error != CL_SUCCESS)
    {
        Log::add().error("initDevice",errorToString(error));
        return false;
    }

    // Create an OpenCL context
    cl_context context = clCreateContext( NULL, 1, &device_id, NULL, NULL, &error);
    if(error != CL_SUCCESS)
    {
        Log::add().error("initDevice",errorToString(error));
        return false;
    }

    command_queue = clCreateCommandQueue(context, device_id, 0, &error);
    if(error != CL_SUCCESS)
    {
        Log::add().error("initDevice",errorToString(error));
        return false;
    }

    initialized = true;

    return true;
}


cl_context getContext(void)
{
    return context;
}

cl_command_queue getQueue(void)
{
    return command_queue;
}

bool compileProgram(const char* sources, cl_program& program )
{
    cl_int error;
    program = clCreateProgramWithSource(context,
                                        1,
                                        (const char **) &(sources),
                                        NULL,
                                        &error);
    if(error != CL_SUCCESS)
    {
        Log::add().error("compileProgram",errorToString(error));
        return false;
    }
    error = clBuildProgram(program,
                           0,
                           NULL,
                           NULL,
                           NULL,
                           NULL);
    if(error != CL_SUCCESS)
    {
        Log::add().error("compileProgram",errorToString(error));
        char buffer[4096];
        size_t length;
        clGetProgramBuildInfo(program,
                              device_id,
                              CL_PROGRAM_BUILD_LOG,
                              sizeof(buffer),
                              buffer,
                              &length
                             );
        Log::add().error("compileProgram",buffer);
        return false;
    }
    return true;
}


void destroy(void)
{
    if(initialized==true)
    {
        clReleaseCommandQueue(command_queue);
        clReleaseContext(context);
        initialized = false;
    }

}


void showInfo(void)
{
    char buffer[4096];
    size_t length;
    cl_int error = clGetDeviceInfo(device_id,CL_DEVICE_PROFILE,sizeof(buffer),buffer,&length);
    if(error != CL_SUCCESS)
    {
        Log::add().error("compileProgram",errorToString(error));
    }
    else
    {
        Log::add().info(buffer);
    }

}


}

}
